﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceProcess;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using Kobets.Automation.Chimera.Agent.Properties;
using Kobets.Automation.Chimera.Common;
using Kobets.Automation.Chimera.Common.Contracts;
using Kobets.Automation.Chimera.Common.Models;
using Environment = System.Environment;

namespace Kobets.Automation.Chimera.Agent
{
    public partial class ChimeraAgent : ServiceBase
    {
        #region Fields

        private static ServiceHost _s_host;
        private const string SourceName = "Chimera Agent";
        private static readonly Queue<Step> _s_queue = new Queue<Step>();
        private static ICommand _s_executionEngine;
        private static readonly CancellationTokenSource _s_cancellationTokenSource = new CancellationTokenSource();
        private static Task _s_task;

        #endregion

        public static EventLog MyEventLog = new EventLog();
        public static Step CurrentStep;
        public static Common.Agent Agent = new Common.Agent();

        public ChimeraAgent()
        {
            InitializeComponent();
            Agent.Status = Enums.AgentStatus.Idle;
        }

        protected override void OnStart(string[] args)
        {
            if (!EventLog.SourceExists(SourceName))
                EventLog.CreateEventSource(SourceName, "Application");

            MyEventLog.Source = SourceName;
            MyEventLog.WriteEntry("Service started");

            Agent.Name = Environment.MachineName;
            Agent.NetworkName = CommonMethods.GetLocalhostFqdn();
            Agent.Status = Enums.AgentStatus.Idle;
            Agent.Controller = Settings.Default.Controller;

            var baseAddress = new Uri("http://" + CommonMethods.GetLocalhostFqdn() + ":8080/chimeraagent");
            var customHttpBinding = new CustomHttpBinding(false)
                                        {
                                            MaxMessageSize = 52428800,
                                            OpenTimeout = new TimeSpan(0, 10, 0),
                                            CloseTimeout = new TimeSpan(0, 10, 0),
                                            ReceiveTimeout = new TimeSpan(0, 10, 0),
                                            SendTimeout = new TimeSpan(0, 10, 0)
                                        };
            _s_host = new ServiceHost(typeof(AgentContract));
            _s_host.AddServiceEndpoint(typeof (IAgentContract), customHttpBinding, baseAddress);
            _s_host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
            _s_host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
            _s_host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByIssuerName, "Chimera");
            var smb = new ServiceMetadataBehavior { HttpGetEnabled = true, MetadataExporter = { PolicyVersion = PolicyVersion.Policy15 }, HttpGetUrl = baseAddress };
            _s_host.Description.Behaviors.Add(smb);
            _s_host.Open();

            MyEventLog.WriteEntry("Comm channel open on " + baseAddress);
        }

        protected override void OnStop()
        {
            OnStopInternal();
        }

        #region Public Methods

        public static void RunSteps(IEnumerable<Step> steps)
        {
            if (steps == null) throw new ArgumentNullException("steps");

            foreach (var step in steps)
                _s_queue.Enqueue(step);

            if ( Agent.Status == Enums.AgentStatus.Idle )
            {
                MyEventLog.WriteEntry("Starting execution...");
                Agent.Status = Enums.AgentStatus.Running;
                ProcessWorkUnits();
            }
        }

        public static void StopExecution()
        {
            if ( _s_task.Status != TaskStatus.Running ) return;
            _s_executionEngine.Abort();
            _s_cancellationTokenSource.Cancel();
            MyEventLog.WriteEntry("Stopping execution...");
            Agent.Status = Enums.AgentStatus.Idle;
        }

        public static void Restart()
        {
            if (Agent.Status == Enums.AgentStatus.Running) StopExecution();
            OnStopInternal();
            Environment.Exit(1);
        }

        #endregion

        #region Private Methods

        private static void OnStopInternal()
        {
            if (_s_host != null && _s_host.State == CommunicationState.Opened) _s_host.Close();
            Settings.Default.Controller = Agent.Controller;
            Settings.Default.Save();
            MyEventLog.WriteEntry("Service stopped");
        }

        /// <summary>
        /// Main scenario here
        /// </summary>
        private static void ProcessWorkUnits()
        {
            do
            {
                CurrentStep = _s_queue.Dequeue();
           
                try
                {
                    // check if deferred step and wait until dependency satisfied
                    if (CurrentStep.WaitFor != null)
                    {
                        using (var db = new ChimeraContext())
                        {
                            if ( db.Jobs.Single(s => s.Id == CurrentStep.WaitFor.Id).Status !=
                                 CommonMethods.GetJobStatus(Enums.JobStatus.Completed) )
                            {
                                Thread.Sleep(1000);
                            }
                        }
                    }

                    if (CurrentStep.Command == null) continue;
                    var executionEngineType = Type.GetType(CurrentStep.JobType.Name + "Command", true, true);
                    _s_executionEngine = (ICommand)Activator.CreateInstance(executionEngineType);
                    _s_executionEngine.CommandComplete += ExecutionEngineOnCommandComplete;
                    _s_executionEngine.CommandFailed += ExecutionEngineOnCommandFailed;
                    _s_executionEngine.CommandInProgress += ExecutionEngineOnCommandInProgress;
                    _s_task = Task.Factory.StartNew(() => _s_executionEngine.Execute(CurrentStep.Command.Content, CurrentStep.Id, _s_cancellationTokenSource.Token), _s_cancellationTokenSource.Token);
                    _s_task.Wait(TimeSpan.FromMinutes(30)); // abort any process running more then 30 minutes
                }
                catch (Exception ex)
                {
                    MyEventLog.WriteEntry(ex.Message);
                }
            } while (_s_queue.Count != 0);
        }

        private static void ExecutionEngineOnCommandInProgress(object sender, EventArgs<Guid> eventArgs)
        {
            // update job as InProgress
            using (var db = new ChimeraContext())
            {
                db.Jobs.Single(s => s.StepId == eventArgs.Value).Status = CommonMethods.GetJobStatus(Enums.JobStatus.InProgress);
                db.SaveChanges();
            }
        }

        private static void ExecutionEngineOnCommandFailed(object sender, EventArgs<KeyValuePair<Guid, Exception>> eventArgs)
        {
            // update job as Failed
            using (var db = new ChimeraContext())
            {
                db.Jobs.Single(s => s.StepId == eventArgs.Value.Key).Status = CommonMethods.GetJobStatus(Enums.JobStatus.Failed);
                db.SaveChanges();
            }

            DestroyExecutionEngine();
            MyEventLog.WriteEntry(eventArgs.Value.Value.Message, EventLogEntryType.Error);
        }

        private static void ExecutionEngineOnCommandComplete(object sender, EventArgs<Guid> eventArgs)
        {
            // update job as Completed
            using (var db = new ChimeraContext())
            {
                db.Jobs.Single(s => s.StepId == eventArgs.Value).Status = CommonMethods.GetJobStatus(Enums.JobStatus.Completed);
                db.SaveChanges();
            }

            DestroyExecutionEngine();
        }

        private static void DestroyExecutionEngine()
        {
            _s_executionEngine.CommandComplete -= ExecutionEngineOnCommandComplete;
            _s_executionEngine.CommandFailed -= ExecutionEngineOnCommandFailed;
            _s_executionEngine.CommandInProgress -= ExecutionEngineOnCommandInProgress;
            _s_executionEngine = null;
        }

        #endregion
    }
}
